import { mat4, vec3 } from "gl-matrix"
import { m4Identity } from "./constant"
// 画白色方块
export function drawScene(
  gl,
  programInfo,
  buffers,
  camera,
  isInverting,
  showTraversal,
  showRemaining
) {
  gl.enable(gl.DEPTH_TEST) // Enable 深度測試
  gl.depthFunc(gl.ALWAYS) // 近处的事物掩盖了远处的事物
  // 告诉 WebGL 如何从位置中拉出位置
  // 缓冲区添加到 vertexPosition 属性中。
  {
    const numComponents = 2 // 每次迭代拉出 2 个值
    const type = gl.FLOAT // 缓冲区中的数据是 32 位浮点数
    const normalize = false // 不要规范化
    const stride = 0 // 从一组值到下一组值获取多少字节
    // 0 = 使用上面的 type 和 numComponents
    const offset = 0 // 缓冲区内的字节数
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position)
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexPosition,
      numComponents,
      type,
      normalize,
      stride,
      offset
    )
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition)
  }

  // 告诉 WebGL 在绘图时使用我们的程序
  gl.useProgram(programInfo.program)

  gl.uniformMatrix4fv(
    programInfo.uniformLocations.projectionMatrix,
    false,
    camera.perspective
  )
  gl.uniformMatrix4fv(
    programInfo.uniformLocations.modelViewMatrix,
    false,
    camera.view
  )
  gl.uniform1i(programInfo.uniformLocations.isInverting, isInverting)
  gl.uniform1i(programInfo.uniformLocations.showTraversal, showTraversal)
  gl.uniform1i(programInfo.uniformLocations.showRemaining, showRemaining)

  {
    const offset = 0
    const vertexCount = 4
    gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount)
  }
}

export function initBuffers(gl, width, height) {
  // 建立一個 buffer 來儲存正方形的座標

  const positionBuffer = gl.createBuffer()

  // 选择 positionBuffer 作为要应用缓冲区的
  // 操作从这里开始。

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)

  // 现在为正方形创建一个位置数组。
  const positions = [0, 0, width, 0, 0, -height, width, -height]

  // 现在将位置列表传递到 WebGL 中以构建
  // 形状。为此，我们从
  // JavaScript 数组，然后使用它来填充当前缓冲区。

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)

  return {
    position: positionBuffer,
  }
}
export function loadShader(gl, type, source) {
  const shader = gl.createShader(type)

  // 将源发送到着色器对象

  gl.shaderSource(shader, source)

  // 编译着色器程序

  gl.compileShader(shader)

  // 查看编译是否成功

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error(
      `An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`
    )
    gl.deleteShader(shader)
    return null
  }

  return shader
}

//
// 初始化 shader 來告知WebGL怎麼畫
//
export function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)

  // 建立 shader 程式
  const shaderProgram = gl.createProgram()
  gl.attachShader(shaderProgram, vertexShader)
  gl.attachShader(shaderProgram, fragmentShader)
  gl.linkProgram(shaderProgram)

  // 錯誤處理
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    console.error(
      `Unable to initialize the shader program: ${gl.getProgramInfoLog(
        shaderProgram
      )}`
    )
    return null
  }

  return shaderProgram
}

const defaultVsSource = `
  attribute vec4 aVertexPosition;

  uniform mat4 uModelViewMatrix;
  uniform mat4 uProjectionMatrix;

  void main() {
    gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
  }
`

const defaultFsSource = `
  precision mediump float;
  uniform bool isInverting;
  void main() {
    if (isInverting) {
      gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
  }
`

export const generateProgramInfo = (
  gl,
  vsSource = defaultVsSource,
  fsSource = defaultFsSource
) => {
  const shaderProgram = initShaderProgram(gl, vsSource, fsSource)
  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(
        shaderProgram,
        "uProjectionMatrix"
      ),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
      isInverting: gl.getUniformLocation(shaderProgram, "isInverting"),
      showTraversal: gl.getUniformLocation(shaderProgram, "showTraversal"),
      showRemaining: gl.getUniformLocation(shaderProgram, "showRemaining"),
    },
  }
  return programInfo
}

export function objectHasMatchingFields(obj, fields) {
  for (const key in fields)
    if (fields.hasOwnProperty(key) && obj[key] !== fields[key]) return false
  return true
}

export function calcCamera({
  viewportWidth,
  viewportHeight,
  fovy,
  near,
  far,
  eye,
  center,
  up,
  showPerspective,
  machineX,
  machineY,
}) {
  let perspective
  // @ts-ignore
  let view = mat4.lookAt([], eye, center, up)
  // @ts-ignore
  view = mat4.translate([], view, [-machineX, -machineY, 0])
  const yBound = vec3.distance(eye, center) * Math.tan(fovy / 2)
  const scale = viewportHeight / (2 * yBound)
  if (showPerspective) {
    perspective = mat4.perspective(
      new Float32Array(),
      fovy,
      viewportWidth / viewportHeight,
      near,
      far
    )
  } else {
    // @ts-ignore ●
    perspective = mat4.identity([])
    // @ts-ignore
    view = mat4.mul(
      // @ts-ignore
      [],
      // @ts-ignore
      // -viewportWidth , viewportWidth, -viewportHeight, viewportHeight, near, far),
      mat4.ortho(
        [],
        -yBound * (viewportWidth / viewportHeight),
        yBound * (viewportWidth / viewportHeight),
        -yBound,
        yBound,
        near,
        far
      ),
      view
    )
    fovy = 0
  }
  // @ts-ignore
  const viewInv = mat4.invert([], view)
  return {
    fovy,
    perspective,
    view,
    viewInv,
    scale,
  }
}

function cacheDrawing(fn, state, args) {
  const { drawCommands, width, height } = args
  // 检查当前状态对象 state 是否与传入的参数 args 匹配。如果它们不匹配，意味着绘制所需的参数发生了变化，需要进行缓存处理。
  if (!objectHasMatchingFields(state, args)) {
    // 遍历传入的参数 args 对象，将其属性复制给当前状态对象 state，以更新状态对象以反映最新的绘制参数。
    for (const key in args) if (args.hasOwnProperty(key)) state[key] = args[key]
    // 如果状态对象中没有帧缓冲区frameBuffer,则调用 drawCommands.createFrameBuffer 方法创建一个新的帧缓冲区，并将其存储在状态对象中。
    if (!state.frameBuffer)
      state.frameBuffer = drawCommands.createFrameBuffer(width, height)
    // 如果帧缓冲区已经存在，则调用 resize() 方法重新调整其大小以适应新的宽度和高度。
    else state.frameBuffer.resize(width, height)
    // 将渲染目标切换到状态对象中的帧缓冲区 frameBuffer，然后清除颜色缓冲区和深度缓冲区。接着调用传入的绘制函数 fn，并传递参数 args
    drawCommands.useFrameBuffer(state.frameBuffer, () => {
      drawCommands.gl.clearColor(1, 1, 1, 0)
      drawCommands.gl.clear(
        drawCommands.gl.COLOR_BUFFER_BIT | drawCommands.gl.DEPTH_BUFFER_BIT
      )
      fn(args)
    })
  }
  // 如果状态对象未更新，则直接绘制图像。这里使用帧缓冲区的纹理作为绘制的图像，并传递一些固定的参数（例如透视矩阵、视图矩阵、是否选择等）给绘 制命令。
  drawCommands.image({
    perspective: m4Identity,
    view: m4Identity,
    texture: state.frameBuffer.texture,
    selected: false,
    transform2d: [2 / width, 0, 0, -2 / height, -1, 1],
  })
}

export const drawTaskPreview = (
  taskPreview,
  cachedDrawState,
  canvas,
  drawCommands,
  workspace,
  camera,
  drawingArgs
) => {
  const {
    isInverting = false,
    showTraversal = false,
    showRemaining = false,
  } = drawingArgs
  const draw = () => {
    taskPreview.draw(
      drawCommands,
      camera.perspective,
      camera.view,
      workspace.g0Rate,
      workspace.simTime,
      workspace.rotaryDiameter,
      isInverting,
      showTraversal,
      showRemaining
    )
  }
  cacheDrawing(draw, cachedDrawState, {
    drawCommands,
    width: canvas.width,
    height: canvas.height,
    perspective: camera.perspective,
    view: camera.view,
    g0Rate: workspace.g0Rate,
    simTime: workspace.simTime,
    rotaryDiameter: workspace.rotaryDiameter,
    isInverting,
    showTraversal,
    showRemaining,
    arrayVersion: taskPreview.arrayVersion,
  })
}
